/*
*  Copyright (c) 2001 Sun Microsystems, Inc.  All rights
*  reserved.
*
*  Redistribution and use in source and binary forms, with or without
*  modification, are permitted provided that the following conditions
*  are met:
*
*  1. Redistributions of source code must retain the above copyright
*  notice, this list of conditions and the following disclaimer.
*
*  2. Redistributions in binary form must reproduce the above copyright
*  notice, this list of conditions and the following disclaimer in
*  the documentation and/or other materials provided with the
*  distribution.
*
*  3. The end-user documentation included with the redistribution,
*  if any, must include the following acknowledgment:
*  "This product includes software developed by the
*  Sun Microsystems, Inc. for Project JXTA."
*  Alternately, this acknowledgment may appear in the software itself,
*  if and wherever such third-party acknowledgments normally appear.
*
*  4. The names "Sun", "Sun Microsystems, Inc.", "JXTA" and "Project JXTA"
*  must not be used to endorse or promote products derived from this
*  software without prior written permission. For written
*  permission, please contact Project JXTA at http://www.jxta.org.
*
*  5. Products derived from this software may not be called "JXTA",
*  nor may "JXTA" appear in their name, without prior written
*  permission of Sun.
*
*  THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
*  WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
*  OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
*  DISCLAIMED.  IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR
*  ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
*  SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
*  LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
*  USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
*  ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
*  OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
*  OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
*  SUCH DAMAGE.
*  ====================================================================
*
*  This software consists of voluntary contributions made by many
*  individuals on behalf of Project JXTA.  For more
*  information on Project JXTA, please see
*  <http://www.jxta.org/>.
*
*  This license is based on the BSD license adopted by the Apache Foundation.
*
*  $Id: Env.java,v 1.42 2007/04/11 19:02:36 nano Exp $
*/

package net.jxta.myjxta.util;

import net.jxta.ext.config.Resource;
import net.jxta.ext.config.ResourceManager;
import net.jxta.ext.config.ResourceNotFoundException;
import net.jxta.logging.Logging;

import java.io.*;
import java.net.MalformedURLException;
import java.net.URI;
import java.net.URISyntaxException;
import java.net.URL;
import java.util.Properties;
import java.util.logging.Level;
import java.util.logging.Logger;

/**
 * A class that defined various environment variables
 *
 * @author james todd [gonzo at jxta dot org]
 * @author mike mcangus [mcangus at jxta dot org]
 * @version $Id: Env.java,v 1.42 2007/04/11 19:02:36 nano Exp $
 */

public final class Env {
    /**
     * The  directory relative to which all path information is handled
     */
    public static final String MYJXTA = "JXTA_HOME";
    public static final String DEFAULT_HOME = "user.home";

    public static final String HTTP_PROXY_HOST = "http.proxyHost";
    public static final String HTTP_PROXY_PORT = "http.proxyPort";

    /**
     * The directory in which preference and configuration info is saved
     */
    public static final String HOME = ".csvnp";

    public static final String PREFS = "prefs.xml";
    public static final String DEFAULT_PREFS = "/net/jxta/myjxta/resources/" + PREFS;

    /**
     * The directory structure under which runtime config and property
     * files reside
     */
    public static final String RESOURCES = "/resources/";

    /**
     * The environment string that indicates the user name
     */
    public static final String PRINCIPAL = "net.jxta.tls.principal";

    /**
     * The environment string that indicated the selected password
     */
    public static final String PASSWORD = "net.jxta.tls.password";

    public static final String CONFIG_URL = "net.jxta.myjxta.config.url";

    /**
     * The file the contains configuration information written by
     * the platform code
     */
    public static final String PLATFORM = "PlatformConfig";

    public static final String PSE = "pse";
    public static final String RECONFIGURE = "reconf";

    public static final String DEFAULT_CONFIG =
            "file:conf/PublicAdminRules.xml";
    public static final String CONFIGURATION = "profile.xml";
    public static final String DEFAULT_CONFIGURATION =
            "/net/jxta/myjxta/resources/" + CONFIGURATION;
    public static final String LOG4J = "log4j.xml";
    public static final String DEFAULT_LOG4J =
            "/net/jxta/myjxta/resources/" + LOG4J;
    public static final String CMS = "data";
    public static final String SHARE = "share";
    public static final String TMP = "tmp";
    public static final String TCP_PORT = System.getProperty("jxta.tcp.port");
    public static final String CONSTANTS =
            "/net/jxta/myjxta/resources/myjxta.xml";
    public static final String PLATFORM_CONFIG = "config.properties";
    public static final String PLATFORM_CONFIG_COMMENT =
            "generated from myjxta.xml";
    public static final String JXTA_CACHE = "cm";
    public static final String ADMINISTRATION_PORT = "myjxta.sid";
    public static final int DEFAULT_ADMINISTRATION_PORT = 10701;
    public static final int MAX_ADMINISTRATION_PORT_TRY = 1000;
    public static final int MAX_ADMINISTRATION_TIME = 7 * 1000;

    /**
     * The number of minutes before we res-send a ping  signal on the ManyToMany Chat
     */
    public static final int PING_INTERVAL = 5;

    /**
     * The interval between tree updates  in minutes
     */
    public static final int TREE_UPDATE_PERIOD = 3;

    /**
     * The ping message to send
     */
    public static final String PING_MESSAGE = "**** ping ****";
    public static final String PING_COMMAND = "ping";

    private static final String COLON = ":";
    private static final String SLASH = "/";
    //    private static final String PIPE = "|";
    private static final String URI_DELIMITER = COLON + SLASH;
    private static final String URL_DELIMITER = URI_DELIMITER + SLASH;
    private static final Logger LOG = Logger.getLogger(Env.class.getName());

    /**
     * Home directory for configuration and log files.
     */
    private static URI myjxta = null;

    private static final String log4jConfigFilePath = null;
    private static final boolean log4jConfigFileXml = false;

    private static boolean isInitialized = false;

    /**
     * Initialzes the Env object with a default
     * preference directory of
     * {@link #HOME HOME} and a preference path
     */
    public static void initialize() {
        initialize(HOME);
    }

    /**
     * Initializes the Env class attributes, ensures that required files exists and initializes Log4J logging.
     *
     * @param base the base directory for the configuration information, relative to the user's home directory.
     */
    public static synchronized void initialize(String base) {
        // retrieve  the desired working directory
        if (!isInitialized) {
            String home = initHome(base);

            if (!home.endsWith(File.separator)) {
                home += File.separator;
            }

            initMyJxtaConfig(home);

            isInitialized = true;
        }
    }

    /**
     * Initializes base directory URI, {@link #myjxta}, and returns a String
     * representing the URI.
     *
     * @param base the base directory for the configuration information, relative to the user's home directory.
     */
    private static String initHome(String base) {
        // retrieve  the desired working directory
        base = base != null ? base.trim() : HOME;

        if (base.length() == 0) {
            base = HOME;
        }

        String mh = System.getProperty(MYJXTA);

        mh = mh != null ? mh.trim() : null;

        if (mh == null ||
                mh.length() == 0) {
            // If it is not found use the user home directory
            // and create the missing configuration directory

            String uh = System.getProperty(DEFAULT_HOME);

            mh = /*base.startsWith(uh) ? base : uh + File.separator +*/ base;

            System.setProperty(MYJXTA, mh);
        }

        File f = new File(mh);

        if (!f.exists()) {
            f.mkdirs();
        }
        myjxta = f.toURI();

        return getHomeDirPath();
    }

    /**
     * Ensures that the application config files exist in the base directory.
     *
     * @param home The base directory for the configuration information, relative to the user's home directory.
     */
    private static void initMyJxtaConfig(String home) {
        // retrieve  the desired working directory
        File f = new File(home + CONFIGURATION);

        // xxx: drop profile.xml for a period of time; see issue #1514
        //delete(f);

        if (!f.exists()) {
            try {
                createFile(f, DEFAULT_CONFIGURATION);
            } catch (IOException e) {
                LOG.log(Level.SEVERE,
                        "Unable to create file " + f.getAbsolutePath(), e);
            }
        }

        String rc = new File(CONSTANTS).getName();

        f = new File(home + rc);

        // xxx: legacy support hack
        Resource r = new Resource();

        try {
            r.load(f.toURI().toURL());
        } catch (MalformedURLException mue) {
            if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                LOG.log(Level.SEVERE, "Caught unexpected Exception", mue);
            }
        } catch (ResourceNotFoundException rnfe) {
            // Ignored.  We deal with missing myjxta.xml below.
        }

        if (!f.exists() ||
                r.get(Constants.NETWORK) == null) {
            try {
                createFile(f, CONSTANTS);
            } catch (IOException e) {
                LOG.log(Level.SEVERE,
                        "Unable to create file " + f.getAbsolutePath(), e);
            }
        }

        Constants c = Constants.getInstance();

        r = new Resource();

        try {
            r.load(CONSTANTS);
        } catch (ResourceNotFoundException rnfe) {
            if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                LOG.log(Level.SEVERE, "Caught unexpected Exception", rnfe);
            }
        }

        c.set(Constants.NAME, r.get(Constants.NAME, c.get(Constants.NAME)));
        c.set(Constants.VERSION, r.get(Constants.VERSION, c.get(Constants.NAME)));
        c.set(Constants.BUILD, r.get(Constants.BUILD, c.get(Constants.BUILD)));
        c.save();

        String ph = System.getProperty(HTTP_PROXY_HOST);
        String pp = System.getProperty(HTTP_PROXY_PORT,
                String.valueOf(Constants.DEFAULT_HTTP_PROXY_PORT));

        if (ph != null) {
//            URL pu = null;

            try {
                c.set(Constants.PROXY_HTTP,
                        new URL(Constants.PROTOCOL_HTTP + URL_DELIMITER + ph +
                                COLON + pp).toString());
            } catch (MalformedURLException mue) {
                if (Logging.SHOW_FINE && LOG.isLoggable(Level.FINE)) {
                    LOG.log(Level.FINE, "invalid url: " + ph + " " + pp, mue);
                }
            }
        }

        // xxx: hack; in time, support loading multiple networks
        String nid = c.get(Constants.NETWORK_ID, "").trim();

        if (nid.length() > 0) {
            Properties p = new Properties();

            p.put(Constants.NET_PEER_GROUP_ID_KEY, nid);
            p.put(Constants.NET_PEER_GROUP_NAME_KEY,
                    c.get(Constants.NETWORK_NAME, "").trim());
            p.put(Constants.NET_PEER_GROUP_DESCRIPTION_KEY,
                    c.get(Constants.NETWORK_DESCRIPTION, "").trim());

            try {
                p.store(new FileOutputStream(new File(home + Env.PLATFORM_CONFIG)),
                        Env.PLATFORM_CONFIG_COMMENT);
            } catch (FileNotFoundException fnfe) {
                if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                    LOG.log(Level.SEVERE,
                            "can\'t write " + Env.PLATFORM_CONFIG + " file",
                            fnfe);
                }
            } catch (IOException ioe) {
                if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                    LOG.log(Level.SEVERE,
                            "can\'t write " + Env.PLATFORM_CONFIG + " file", ioe);
                }
            }
        }
        initPreferences(home);

        // xxx: drop the cm for a period of time; see issue #331
//        delete(new File(home + JXTA_CACHE));
    }

    private static void initPreferences(String home) {
        File f = new File(home + PREFS);

        if (!f.exists()) {
            try {
                createFile(f, DEFAULT_PREFS);

            } catch (IOException e) {
                LOG.log(Level.SEVERE,
                        "Unable to create file " + f.getAbsolutePath(),
                        e);
            }
        }


    }

    /**
     * Returns the URI path of the Home Directory.
     */
    public static URI getHome() {
        return myjxta;
    }

    /**
     * Returns the {@link File#getCanonicalPath() Canonical} path of the Home Directory if possible; otherwise,
     * returns the {@link File#getAbsolutePath() Absolute} path.
     */
    public static String getHomeDirPath() {
        String p = null;

        if (myjxta != null) {
            try {
                p = Conversion.toFile(myjxta).getCanonicalPath();
            } catch (ConversionException ce) {
                if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                    LOG.log(Level.SEVERE, "home path", ce);
                }
            } catch (IOException ioe) {
                if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                    LOG.log(Level.SEVERE, "home path", ioe);
                }
            }
        }

        return p;
    }

    /**
     * Returns the URL of the current configuration file.
     */
    public static URL getConfiguration() {
        URL u = null;

        try {
            u = new URL(getHome() + CONFIGURATION);
        } catch (MalformedURLException mue) {
            if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                LOG.log(Level.SEVERE, "Caught unexpected Exception", mue);
            }
        }

        return u;
    }

    /**
     * Return the path to platform specific configuration
     * file
     *
     * @return the path to the platform specific configuration file
     */
    public static URL getPlatform() {
        URL u = null;

        try {
            u = new URL(getHome() + PLATFORM);
        } catch (MalformedURLException mue) {
            if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                LOG.log(Level.SEVERE, "Caught unexpected Exception", mue);
            }
        }

        return u;
    }

    /**
     * Creates or deletes a Reconfigure file (home/reconf) depending on
     * whether the reconfigure param is true or false, respectively.
     *
     * @param reconfigure If true, a new Reconfigure file is created;
     *                    otherwise, the existing Reconfigure file is deleted.
     */
    public static void setReconfigure(boolean reconfigure) {
        File r = new File(new File(myjxta), RECONFIGURE);

        if (reconfigure) {
            try {
                r.createNewFile();
            } catch (IOException ioe) {
                if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                    LOG.log(Level.SEVERE, "Caught unexpected Exception", ioe);
                }
            }
        } else {
            delete(r);
        }
    }

    /**
     * @return The log4jConfigFilePath.
     */
    public static String getLog4jConfigFilePath() {
        return log4jConfigFilePath;
    }

    /**
     * @return true if the log4j config file pointed to by log4jConfigFilePath has a ".xml" extension, and false
     *         otherwise.
     */
    public static boolean isLog4jConfigFileXml() {
        return log4jConfigFileXml;
    }

    public static void delete(File f) {
        if (f.exists()) {
            if (f.isFile()) {
                f.delete();
            } else {
                File[] files = f.listFiles();

                for (File file : files) {
                    delete(file);
                }

                f.delete();
            }
        }
    }

    private static void createFile(File newFile, String sourceFilePath) throws IOException {
        File p = newFile.getParentFile();

        if (p != null) {
            p.mkdirs();
        }

        ResourceManager rm = ResourceManager.getInstance();
        InputStream is = rm.getResourceAsStream(sourceFilePath);

        if (is != null) {
            newFile.createNewFile();

            FileOutputStream os = new FileOutputStream(newFile);
            BufferedOutputStream out = new BufferedOutputStream(os);

            int c;

            while ((c = is.read()) > -1) {
                out.write(c);
            }

            out.flush();
            is.close();
            out.close();
        }
    }

    /**
     * Returns the URL of the current preferences file.
     */
    public static URL getPreferences() {
        URL url = null;

        try {
            url = new URL(getHome() + PREFS);
        } catch (MalformedURLException mue) {
            if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                LOG.log(Level.SEVERE, "Caught unexpected Exception", mue);
            }
        }

        return url;
    }

    public static boolean savePreferences(String prefDoc) {
        boolean ret = false;
        try {
            updateFile(getPreferences(), new ByteArrayInputStream(prefDoc.getBytes()));
        } catch (IOException iox) {
            if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                LOG.log(Level.SEVERE, "Caught unexpected Exception", iox);
            }
        }
        return ret;
    }

    private static void updateFile(URL currentFile, InputStream in) throws IOException {

        File f;
        try {
            f = new File(currentFile.toURI());

            if (!f.exists()) {
                if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                    LOG.severe("File does not exist. Creating");
                }

                File p = f.getParentFile();

                if (p != null) {
                    p.mkdirs();
                }

                f.createNewFile();
            }

            FileOutputStream os = new FileOutputStream(f);
            BufferedOutputStream out = new BufferedOutputStream(os);

            int c;

            while ((c = in.read()) > -1) {
                out.write(c);
            }

            out.flush();
            in.close();
            out.close();
        } catch (URISyntaxException ex) {
            if (Logging.SHOW_SEVERE && LOG.isLoggable(Level.SEVERE)) {
                LOG.log(Level.SEVERE, "Caught unexpected Exception", ex);
            }
        }

    }

}
